package com.toobigtofail.drum
{
	public class Pattern
	{
		// Pattern state
		private var _measures:Array = new Array;
		private var _currentMeasure:uint = 0;
		private var _active:Boolean = false;
		private var _lastHitResult:HitResult = null;
		
		// Timing state
		private var _lastDrumTime:Number = 0;
		private var _bpm:uint; // beats per minutes
		
		public function Pattern(pattern:String, bpm:uint=60) {
			// Accept a pattern in the form "A.A.A|A.AA|A.AA".
			// Each AAA is a measure of any number of beats separated by a | to represent the end of a measure
			var measureString:String = new String;
			var c:String;
			for (var i:int = 0; i < pattern.length; i++) {
				c = pattern.charAt(i);
				if (c == "|") {
					if (measureString.length) {
						_measures.push(new Measure(measureString));
					}
					measureString = new String;
				} else {
					measureString += c;
				}
			}
			
			if (measureString.length) {
				_measures.push(new Measure(measureString));
			}
			
			// Set the tempo in beats per minute
			_bpm = bpm;
		}
		
		public function checkNextNote(key:int):HitResult {
			var measure:Measure = _measures[_currentMeasure];
			var result:HitResult = measure.checkNextNote(key, _bpm, _lastDrumTime);
			
			// Check whether the drum hit matches the next note expected
			if (result.result == HitResult.MEASURE_SUCCESS) {
				_currentMeasure++;
			    if (_currentMeasure == _measures.length) {
				    result.result = HitResult.PATTERN_SUCCESS;
			    }
			}

			// Reset the pattern if we failed to hit a note or the pattern is finished
			switch(result.result) {
				case HitResult.PATTERN_SUCCESS: reset(); break;
				case HitResult.FAILURE: reset(); break;
			}
			
			// Confirm that the timing was within
			_lastDrumTime = (new Date).getTime();
			return result;
		}
		
		public function reset():void {
			_currentMeasure = 0;
			for each (var measure:Measure in _measures) {
				measure.reset();
			}
		}
		
		public function start():void {
			_lastDrumTime = (new Date).getTime();
			_active = true;
		}
		
		public function stop():void {
			_active = false;
			reset();
		}
		
		public function getNote(idx:int):int {
			var c:int = idx;
			var mi:int = _currentMeasure;
			while (mi < _measures.length && (_measures[mi] as Measure).notesLeft < c) {
				c -= (_measures[mi] as Measure).notesLeft;
			}
			if (mi >= _measures.length) {
				return Notes.INVALID;
			}
			return (_measures[mi] as Measure).getNote(c);
		}
		
		public function get nextNote():int {
			return (_measures[_currentMeasure] as Measure).nextNote;
		}
		
		/**
		 * returns the next time a drum hit is expected in milliseconds since the epoch
		 */ 
		public function get nextDrumTime():Number {
			return (_measures[_currentMeasure] as Measure).nextDrumTime(_bpm, _lastDrumTime);
		}
		
		public function get millisBetweenBeats():Number {
			return (_measures[_currentMeasure] as Measure).millisBetweenBeats(_bpm);
		}
		
		public function get active():Boolean { return _active; }
		public function get lastHitResult():HitResult { return _lastHitResult; }
	}
}